#!/usr/bin/python

import gtk, socket, os, sys, subprocess, string
  
class CrapTV:
	def quit_pgrm(self, widget, data=None):
		'''Called when the window is closed or the quit button pressed'''
		print "Thanks for using CrapTV!\nCleaning up..."
		self.mplayer_proc.kill()
		gtk.main_quit()

	def on_back_clicked(self, widget, data=None):
		self.increment_channel(-1)

	def on_forward_clicked(self, widget, data=None):
		self.increment_channel(1)

	def increment_channel(self, increment):
		'''Change the channel by the amount specified'''
		self.ichannel = (self.ichannel + increment) % 100
		# tune_channel returns True only if it receives a valid signal, so keep tuning until we get one.
		# We use modulus to go back to 0 after reaching 99
		while not self.tune_channel(self.ichannel):
			self.ichannel = (self.ichannel + increment) % 100

	def tune_channel(self, ichannel):
		'''Change the channel to ichannel and return True if a signal is detected'''
		# making ichannel an integer since the spinbutton likes to return a float, this is just for consistent printing
		ichannel = int(ichannel)
		self.ichannel = ichannel

		# Change the value of the spinbutton to the current channel (it won't call tune_channel again since we set buttonchange to True)
		self.buttonchange = True
		self.channel.set_value(ichannel)

		# The program ivtv-tune changes channels on my tuner with the -c argument. We run subprocess here, set to print to stdout so that we can read the output
		output = subprocess.Popen(["ivtv-tune","-c","%i"%ichannel], stdout=subprocess.PIPE,stderr=subprocess.STDOUT).communicate()[0]

		# ivtv-tune will tune to any channel, but it says (Signal Detected) on channels that have a signal. So we just check for that and return True if we get it.
		# FIXME: potential infinite loop here if you don't have any signals!
		if string.find(output,"(Signal Detected)") > 0:
			print "Switched to channel", ichannel
			return True
		else:
			print "Warning: %i has no signal"%ichannel

	def on_channelspinner_value_changed(self, widget):
		'''Automatically change channels if the value of the spinner changes, but NOT if it's done automatically by changing channels with the buttons, 
		since we already changed it then'''
		if not self.buttonchange:
			self.tune_channel(widget.get_value())
		self.buttonchange = False

	def on_fullscreen_clicked(self,widget=None):
		'''Toggle fullscreen TV mode'''
		if not self.window.fullscreened:
			print "Entering fullscreen mode"
			# gtk.Window has a nice .fullscreen() function that will neatly fullscreen the window automagically. The Socket doesn't mind
			# this, except that it makes the aspect messed up if your screen isn't correct (FIXME)
			self.window.fullscreen()
			self.window.fullscreened = True
			# Switch the icons in the fullscreen widget
			self.fs_image.hide()
			self.unfs_image.show()
		else:
			self.window.unfullscreen()
			self.window.fullscreened = False
			self.fs_image.show()
			self.unfs_image.hide()

	def on_window_key_press_event(self,widget, key):
		'''Key press event to allow pressing f to fullscreen and unfullscreen'''
		if gtk.gdk.keyval_name(key.keyval).lower() == 'f':
			self.on_fullscreen_clicked()

	def __init__(self):
		welcomestring = " Welcome to CrapTV! -- The insanely backward TV player for Linux! "
		# Print some lovely equals signs and a welcome string!
		print "\n", "="*len(welcomestring), "\n", welcomestring,"\n","="*len(welcomestring)
		print "\nLoading up some stuff..."
    
		# Create a new Builder object and add all the widgets from the gladefile to it
		builder = gtk.Builder()
		builder.add_from_file("%s/craptv.glade"%sys.path[0]) # This line allows for craptv to be run directly because the file association is direct

		self.buttonchange = False
		
		# Any widgets that need to be referenced in code must be gotten manually. Any other widgets need not be named or fetched.
		self.window = builder.get_object("window")
		self.channel = builder.get_object("channel")
		self.drawingarea = builder.get_object("drawingarea")
		self.fs_image = builder.get_object("fs_image")
		self.unfs_image = builder.get_object("unfs_image")

		# Connect all the callback signals automatically (they'll call functions named after them)
		builder.connect_signals(self)

		self.window.fullscreened = False

		# Starting on channel 2 since it's the lowest number channel that exists for my cable
		self.ichannel = 2
		self.tune_channel(self.ichannel)	

		# Construct a socket to put the mplayer video into
		self.socket = gtk.Socket()
		# drawingarea is just a standard gtk.Hbox with one segment
		self.drawingarea.add(self.socket)

		# Make the socket black so that it looks better when the window is first opened
		black = gtk.gdk.Color(red=0, green=0, blue=0, pixel=0)
		self.socket.modify_bg(gtk.STATE_NORMAL,black)

		self.socket.show()

		# The startup command for mplayer. It seems to work well enough, using -noconsolecontrols so that terminal input isn't messed up when
		# shutting down. /dev/video0 is where my tuner's signal goes to. Mplayer has a command argument -wid that will pop it into a widget's
		# ID, given one. So, we just get the ID of the socket and POP, the video is inside our program
		mplayerstart = "mplayer /dev/video0 -vo xv -noconsolecontrols -wid %i" %self.socket.get_id()
		self.mplayer_proc = self.Run(mplayerstart)

        def Run(self,command):
                'Launch quietly, and return the object so that we can kill it later'
		# Need to split the command up so that Popen can handle it correctly
		command = command.split()
                popen_object = subprocess.Popen(command, stdout=open(os.devnull, 'w'),stderr=open(os.devnull,'w'))
                return popen_object

if __name__ == "__main__":
	craptv = CrapTV()
	craptv.window.show()
	gtk.main()

